package com.dragon.common.utils.http;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * OkHttp工具类
 *
 * @author dragon
 * @version 1.0
 * @date 2024/7/19 11:23
 */
public class OkHttpUtils {

    private static final Logger log = LoggerFactory.getLogger(OkHttpUtils.class);

    /**
     * OkHttpClient
     */
    private static volatile OkHttpClient okHttpClient = null;

    /**
     * JSON数据
     */
    public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
    /**
     * 分块数据
     */
    public static final MediaType MULTIPART_FORM_DATA = MediaType.Companion.parse("multipart/form-data; charset=UTF-8");
    /**
     * 表单数据
     */
    public static final MediaType WWW_FORM_URLENCODED = MediaType.Companion.parse("application/x-www-form-urlencoded; charset=UTF-8");


    /**
     * 请求参数
     */
    private final Map<String, Object> params = new HashMap<>(16);
    /**
     * 请求头
     */
    private final Map<String, Object> headers = new HashMap<>(16);
    /**
     * JSON文本
     */
    private String jsonText;
    /**
     * 请求标签
     */
    private String tagId;

    /**
     * 请求地址Builder
     */
    private HttpUrl.Builder httpUrlBuilder = new HttpUrl.Builder();
    /**
     * 请求Builder
     */
    private Request.Builder requestBuilder = new Request.Builder();


    /**
     * 初始化
     */
    private OkHttpUtils() {
        if (Objects.isNull(okHttpClient)) {
            synchronized (OkHttpUtils.class) {
                if (Objects.isNull(okHttpClient)) {
                    // okHttpClient = SpringUtils.getBean(OkHttpClient.class);
                    okHttpClient = new OkHttpClient.Builder()
                        // 自定义配置
                        .build();
                }
            }
        }
    }

    /**
     * 构建OkHttpUtils
     *
     * @return OkHttpUtils
     */
    public static OkHttpUtils builder() {
        return new OkHttpUtils();
    }

    /**
     * 异步回调
     */
    public interface CallBack {
        /**
         * 异步失败方法
         *
         * @param call okHttp调用对象
         * @param e    IO异常
         */
        void onFailure(@NotNull Call call, @NotNull IOException e);

        /**
         * 异步响应方法
         *
         * @param call     okHttp调用对象
         * @param bodyText IO异常
         */
        void onResponse(@NotNull Call call, String bodyText);
    }


    /**
     * 设置请求地址
     *
     * @param httpUrl 请求地址
     * @return OkHttpUtils
     */
    public OkHttpUtils url(String httpUrl) {
        httpUrlBuilder = HttpUrl.get(httpUrl).newBuilder();

        return this;
    }

    /**
     * 设置请求地址
     *
     * @param httpUrl 请求地址
     * @return OkHttpUtils
     */
    public OkHttpUtils url(URL httpUrl) {
        httpUrlBuilder = Objects.requireNonNull(HttpUrl.get(httpUrl)).newBuilder();

        return this;
    }

    /**
     * 添加请求参数
     *
     * @param key   参数名
     * @param value 参数值
     * @return OkHttpUtils
     */
    public OkHttpUtils addParam(String key, Object value) {
        params.put(key, value);

        return this;
    }

    /**
     * 添加请求参数
     *
     * @param params 参数Map
     */
    public OkHttpUtils addParam(Map<String, Object> params) {
        this.params.putAll(params);

        return this;
    }

    /**
     * 添加请求头
     *
     * @param key   请求头名
     * @param value 请求头值
     * @return OkHttpUtils
     */
    public OkHttpUtils addHeader(String key, Object value) {
        headers.put(key, value);

        return this;
    }

    /**
     * 添加请求头
     *
     * @param headers 请求头Map
     * @return OkHttpUtils
     */
    public OkHttpUtils addHeader(Map<String, Object> headers) {
        this.headers.putAll(headers);

        return this;
    }

    /**
     * 添加JSON文本
     *
     * @param jsonText JSON文本
     * @return OkHttpUtils
     */
    public OkHttpUtils addJsonText(String jsonText) {
        this.jsonText = jsonText.trim();

        return this;
    }

    /**
     * 请求标签
     *
     * @param tagId 标签ID
     * @return OkHttpUtils
     */
    public OkHttpUtils tag(String tagId) {
        this.tagId = tagId;

        return this;
    }

    /**
     * 添加文件
     *
     * @param key   参数名
     * @param files 文件列表
     * @return OkHttpUtils
     */
    public OkHttpUtils addFile(String key, File... files) {
        int fileNum = files.length;

        if (fileNum == 1) {
            params.put(key, files[0]);
        } else if (fileNum > 1) {
            List<File> fileList = new ArrayList<>(fileNum);
            Collections.addAll(fileList, files);
            addFile(key, fileList);
        }

        return this;
    }

    /**
     * 添加文件
     *
     * @param key   参数名
     * @param files 文件列表
     * @return OkHttpUtils
     */
    public OkHttpUtils addFile(String key, Collection<File> files) {
        params.put(key, files);

        return this;
    }

    /**
     * 添加文件路径
     *
     * @param key       参数名
     * @param filePaths 文件路径
     * @return OkHttpUtils
     */
    public OkHttpUtils addFilePath(String key, String... filePaths) {
        int filePathNum = filePaths.length;

        if (filePathNum == 1) {
            params.put(key, new File(filePaths[0]));
        } else if (filePathNum > 1) {
            List<String> filePathsList = new ArrayList<>(filePathNum);
            Collections.addAll(filePathsList, filePaths);

            addFilePath(key, filePathsList);
        }

        return this;
    }

    /**
     * 添加文件路径
     *
     * @param key       参数名
     * @param filePaths 文件路径列表
     * @return OkHttpUtils
     */
    public OkHttpUtils addFilePath(String key, Collection<String> filePaths) {
        List<File> files = new ArrayList<>(filePaths.size());
        filePaths.forEach(filePath -> files.add(new File(filePath)));

        params.put(key, files);

        return this;
    }

    /**
     * 添加文件流
     *
     * @param key         参数名
     * @param fileName    文件名
     * @param inputStream 文件流
     * @return OkHttpUtils
     */
    public OkHttpUtils addInputStream(String key, String fileName, InputStream inputStream) {
        InputStreamResource inputStreamResource = new InputStreamResource(inputStream) {
            @Override
            public long contentLength() throws IOException {
                return inputStream.available();
            }

            @Override
            public String getFilename() {
                return fileName;
            }
        };

        params.put(key, inputStreamResource);

        return this;
    }

    /**
     * 添加文件流(追加)
     *
     * @param key         参数名
     * @param fileName    文件名
     * @param inputStream 文件流
     * @return OkHttpUtils
     */
    public OkHttpUtils addInputStreams(String key, String fileName, InputStream inputStream) {
        Object value = params.get(key);

        List<InputStreamResource> inputStreamResources = new ArrayList<>();
        if (value instanceof List<?> valueList) {
            if (!valueList.isEmpty()) {
                List<InputStreamResource> old = valueList.stream()
                    .filter(item -> item instanceof InputStreamResource)
                    .map(item -> (InputStreamResource) item)
                    .toList();
                inputStreamResources.addAll(old);
            }
        }

        InputStreamResource inputStreamResource = new InputStreamResource(inputStream) {
            @Override
            public long contentLength() throws IOException {
                return inputStream.available();
            }

            @Override
            public String getFilename() {
                return fileName;
            }
        };
        inputStreamResources.add(inputStreamResource);

        params.put(key, inputStreamResources);

        return this;
    }

    /**
     * 添加文件流
     *
     * @param key                  参数名
     * @param inputStreamResources 文件流列表
     * @return OkHttpUtils
     */
    public OkHttpUtils addInputStreamResource(String key, InputStreamResource... inputStreamResources) {
        int inputStreamResourceNum = inputStreamResources.length;

        if (inputStreamResourceNum == 1) {
            params.put(key, inputStreamResources[0]);
        } else if (inputStreamResourceNum > 1) {
            List<InputStreamResource> inputStreamResourcesList = new ArrayList<>(inputStreamResourceNum);
            Collections.addAll(inputStreamResourcesList, inputStreamResources);

            addInputStreamResource(key, inputStreamResourcesList);
        }

        return this;
    }

    /**
     * 添加文件流
     *
     * @param key                  参数名
     * @param inputStreamResources 文件流列表
     * @return OkHttpUtils
     */
    public OkHttpUtils addInputStreamResource(String key, Collection<InputStreamResource> inputStreamResources) {
        params.put(key, inputStreamResources);

        return this;
    }

    /**
     * 添加MultipartFile
     *
     * @param key            参数名
     * @param multipartFiles MultipartFile
     * @return OkHttpUtils
     */
    public OkHttpUtils addMultipartFile(String key, MultipartFile... multipartFiles) {
        int multipartFileNum = multipartFiles.length;
        if (multipartFileNum == 1) {
            params.put(key, multipartFiles[0]);
        } else if (multipartFileNum > 1) {
            List<MultipartFile> multipartFilesList = new ArrayList<>(multipartFileNum);
            Collections.addAll(multipartFilesList, multipartFiles);

            addMultipartFile(key, multipartFilesList);
        }

        return this;
    }

    /**
     * 添加MultipartFile
     *
     * @param key            参数名
     * @param multipartFiles MultipartFile列表
     * @return OkHttpUtils
     */
    public OkHttpUtils addMultipartFile(String key, List<MultipartFile> multipartFiles) {
        params.put(key, multipartFiles);

        return this;
    }

    /**
     * 添加文件流
     *
     * @param key                参数名
     * @param byteArrayResources ByteArrayResource列表
     * @return OkHttpUtils
     */
    public OkHttpUtils addByteArrayResource(String key, ByteArrayResource... byteArrayResources) {
        int byteArrayResourceNum = byteArrayResources.length;
        if (byteArrayResourceNum == 1) {
            params.put(key, byteArrayResources[0]);
        } else {
            List<ByteArrayResource> byteArrayResourcesList = new ArrayList<>(byteArrayResourceNum);
            Collections.addAll(byteArrayResourcesList, byteArrayResources);

            addByteArrayResource(key, byteArrayResourcesList);
        }

        return this;
    }

    /**
     * 添加文件流
     *
     * @param key                参数名
     * @param byteArrayResources ByteArrayResource列表
     * @return OkHttpUtils
     */
    public OkHttpUtils addByteArrayResource(String key, Collection<ByteArrayResource> byteArrayResources) {
        params.put(key, byteArrayResources);

        return this;
    }

    /**
     * 设置请求头
     */
    private void setHeader() {
        if (!headers.isEmpty()) {
            headers.forEach((key, value) -> requestBuilder.addHeader(key, value.toString()));
        }
    }

    /**
     * 设置请求标签
     */
    private void setTag() {
        if (StringUtils.isNotBlank(tagId)) {
            requestBuilder.tag(tagId);
        }
    }

    /**
     * 关闭所有请求
     */
    public void cancelAll() {
        okHttpClient.dispatcher().cancelAll();
    }

    /**
     * 关闭指定标签的请求
     *
     * @param tagId 标签ID
     */
    public void cancel(String tagId) {
        if (StringUtils.isNotBlank(tagId)) {
            for (Call runningCall : okHttpClient.dispatcher().runningCalls()) {
                if (tagId.equals(runningCall.request().tag(String.class))) {
                    runningCall.cancel();
                }
            }
        }
    }

    /**
     * 关闭指定标签集合的请求
     *
     * @param tagIds 标签集合
     */
    public void cancel(Collection<String> tagIds) {
        if (CollectionUtils.isNotEmpty(tagIds)) {
            for (Call runningCall : okHttpClient.dispatcher().runningCalls()) {
                if (tagIds.contains(runningCall.request().tag(String.class))) {
                    runningCall.cancel();
                }
            }
        }
    }

    /**
     * 获取请求体
     *
     * @return RequestBody
     */
    private RequestBody getRequestBody() {
        RequestBody requestBody;

        if (Objects.nonNull(jsonText) && !jsonText.isEmpty()) {
            requestBody = RequestBody.Companion.create(jsonText, JSON);
            requestBuilder = new Request.Builder().post(requestBody);
        } else {
            // 表单数据，不包含文件
            // FormBody.Builder formBodyBuilder = new FormBody.Builder();
            // 分块数据，包含文件
            MultipartBody.Builder multipartBodyBuilder = new MultipartBody.Builder();
            if (!params.isEmpty()) {
                params.forEach((key, value) -> formDataPartHandle(multipartBodyBuilder, key, value));
            }
            requestBody = multipartBodyBuilder.build();
        }

        return requestBody;
    }

    /**
     * 分块数据处理
     *
     * @param multipartBodyBuilder multipartBodyBuilder
     * @param key                  key
     * @param value                value
     */
    private void formDataPartHandle(MultipartBody.Builder multipartBodyBuilder, String key, Object value) {
        if (value instanceof Collection<?> collections) {
            collections.forEach(collection -> formDataPartHandle(multipartBodyBuilder, key, collection));
        } else if (value instanceof File file) {
            // File文件
            multipartBodyBuilder.addFormDataPart(
                key,
                file.getName(),
                RequestBody.Companion.create(file, MULTIPART_FORM_DATA)
            );
        } else if (value instanceof InputStream inputStream) {
            // InputStream
            try {
                multipartBodyBuilder.addFormDataPart(
                    key,
                    "",
                    RequestBody.Companion.create(inputStream.readAllBytes(), MULTIPART_FORM_DATA)
                );
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } else if (value instanceof InputStreamResource inputStreamResource) {
            // InputStreamResource
            try {
                multipartBodyBuilder.addFormDataPart(
                    key,
                    inputStreamResource.getFilename(),
                    RequestBody.Companion.create(inputStreamResource.getContentAsByteArray(), MULTIPART_FORM_DATA)
                );
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } else if (value instanceof MultipartFile multipartfile) {
            // MultipartFile
            try {
                ByteArrayResource byteArrayResource = new ByteArrayResource(multipartfile.getBytes()) {
                    @Override
                    public String getFilename() {
                        return multipartfile.getOriginalFilename();
                    }
                };
                multipartBodyBuilder.addFormDataPart(
                    key,
                    multipartfile.getOriginalFilename(),
                    RequestBody.Companion.create(byteArrayResource.getContentAsByteArray(), MULTIPART_FORM_DATA)
                );
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } else if (value instanceof ByteArrayResource byteArrayResource) {
            // ByteArrayResource
            try {
                multipartBodyBuilder.addFormDataPart(
                    key,
                    byteArrayResource.getFilename(),
                    RequestBody.Companion.create(byteArrayResource.getContentAsByteArray(), MULTIPART_FORM_DATA)
                );
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } else {
            multipartBodyBuilder.addFormDataPart(key, value.toString());
        }
    }


    /**
     * GET请求
     *
     * @return OkHttpUtils
     */
    public OkHttpUtils get() {
        requestBuilder = new Request.Builder().get();

        if (!params.isEmpty()) {
            params.forEach((key, value) -> httpUrlBuilder.addQueryParameter(key, value.toString()));
        }

        requestBuilder.url(httpUrlBuilder.build());

        return this;
    }

    /**
     * POST请求
     *
     * @return OkHttpUtils
     */
    public OkHttpUtils post() {
        requestBuilder = requestBuilder.url(httpUrlBuilder.build()).post(getRequestBody());

        return this;
    }

    /**
     * PUT请求
     *
     * @return OkHttpUtils
     */
    public OkHttpUtils put() {
        requestBuilder = requestBuilder.url(httpUrlBuilder.build()).put(getRequestBody());

        return this;
    }

    /**
     * PATCH请求
     *
     * @return OkHttpUtils
     */
    public OkHttpUtils patch() {
        requestBuilder = requestBuilder.url(httpUrlBuilder.build()).patch(getRequestBody());

        return this;
    }

    /**
     * DELETE请求
     *
     * @return OkHttpUtils
     */
    public OkHttpUtils delete() {
        requestBuilder = requestBuilder.url(httpUrlBuilder.build()).delete(getRequestBody());

        return this;
    }


    /**
     * 同步请求
     *
     * @return 请求结果
     */
    public String sync() throws IOException {
        setHeader();
        setTag();

        try (Response response = okHttpClient.newCall(requestBuilder.build()).execute()) {
            if (Objects.isNull(response.body())) {
                return null;
            }

            return response.body().string();
        }
    }

    /**
     * 异步请求
     *
     * @param callBack 回调
     */
    public void aSync(final CallBack callBack) {
        setHeader();
        setTag();

        okHttpClient.newCall(requestBuilder.build()).enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                callBack.onFailure(call, e);
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                // response.isSuccessful() 检查状态码是否在200-299范围内
                if (Objects.nonNull(response.body())) {
                    callBack.onResponse(call, response.body().string());
                } else {
                    callBack.onResponse(call, null);
                }
            }
        });
    }

}
